package com.xlongwei.info.store;

import java.io.BufferedWriter;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.xnio.IoUtils;

import com.networknt.utility.StringUtils;
import com.xlongwei.info.hook.Shutdown;
import com.xlongwei.info.model.Info;
import com.xlongwei.info.model.User;
import com.xlongwei.info.util.InfoConfig;
import com.xlongwei.info.util.Utils;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class MapStore implements Store, Closeable {
	public static Map<String, User> users = new ConcurrentHashMap<>();
	public static Map<String, Info> infos = new ConcurrentHashMap<>();
	private File file = new File("map.store"), backup = new File("map.csv");
	private BufferedWriter writer = null;

	public MapStore() {
		log.info("MapStore is loaded.");
		loadData();
		Shutdown.add(this);
	}

	@SuppressWarnings("unchecked")
	private void loadData() {
		log.info("loadData");
		boolean exists = file.exists();
		log.info("file.exists={} path={}", exists, file.getAbsolutePath());
		if (exists == false) {
			return;
		}
		log.info("file.length={}", file.length());
		try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(file))) {
			users.putAll((Map<String, User>) in.readObject());
			infos.putAll((Map<String, Info>) in.readObject());
			log.info("users={} infos={}", users.size(), infos.size());
		} catch (Exception e) {
			log.warn("fail to loadData: {}", e.getMessage());
		}
	}

	private void saveData() {
		log.info("saveData");
		try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file))) {
			out.writeObject(users);
			out.writeObject(infos);
			out.close();
			log.info("users={} infos={}", users.size(), infos.size());
			log.info("file.length={} path={}", file.length(), file.getAbsolutePath());
		} catch (Exception e) {
			log.warn("fail to saveData: {}", e.getMessage());
		}
	}

	@Override
	public void close() throws IOException {
		saveData();
		IoUtils.safeClose(writer);
	}

	@Override
	public User user(String username) {
		return StringUtils.isBlank(username) ? null : users.get(username);
	}

	@Override
	public void save(User user) {
		users.put(user.getUser(), user);
	}

	@Override
	public Collection<User> users() {
		return users.values();
	}

	@Override
	public boolean save(Info info) {
		infos.put(info.getUser(), info);
		return true;
	}

	@Override
	public Collection<Info> infos() {
		return infos.values();
	}

	@Override
	public Info delete(String username) {
		return infos.remove(username);
	}

	@Override
	public Info info(String username) {
		return infos.get(username);
	}

	@Override
	public void backup(Info info) {
		if (InfoConfig.INSTANCE.isBackup() == false || info.getAcceptors().isEmpty()) {
			return;
		}
		try {
			char separator = ',';
			if (writer == null) {
				writer = new BufferedWriter(
						new OutputStreamWriter(new FileOutputStream(backup), StandardCharsets.UTF_8));
				if (backup.length() <= 0) {
					writer.write(StringUtils.join(
							Arrays.asList("user", "from", "to", "owner", "start", "end", "acceptors", "note"),
							separator));
					writer.newLine();
				}
			}
			writer.write(StringUtils.join(Arrays.asList(info.getUser(), info.getFrom(), info.getTo(), info.isOwner(),
					Utils.dtf.format(info.getStart()), Utils.dtf.format(info.getEnd()),
					StringUtils.join(info.getAcceptors(), ' '), info.getNote()), separator));
			writer.newLine();
			writer.flush();
		} catch (Exception e) {
			log.warn("fail to backup: {}", e.getMessage());
		}
	}
}
